#include <ctype.h>
#include <rid.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fn85.h>
#include <rid_fn85.h>

#define NIT_SHORT_NAMES
#include <nit/list.h>
#include <nit/set.h>
#include <nit/map.h>
#include <nit/buf.h>
#include <nit/gap.h>
#include <nit/crs.h>

#include "../aas.h"
#include "../aas_parse.h"

#define pass(str) (sizeof(str) - 1)

typedef enum { INVALID, PRINT, RULE, UNRULE, ADD, REMOVE } Command;

Command
get_command(const char *str, size_t *pos)
{
	switch (*str) {
	case 'P':
		if (strncmp(str, "Print", pass("Print")) ||
		    !isspace(str[pass("Print")]))
			break;

		*pos += pass("Print ");
		return PRINT;
	case 'R':
		if (!strncmp(str, "Rule ", pass("Rule "))) {
			*pos += pass("Rule ");
			return RULE;
		}

		if (!strncmp(str, "Remove ", pass("Remove "))) {
			*pos += pass("Remove ");
			return REMOVE;
		}

		break;
	case 'U':
		if (strncmp(str, "Unrule ", pass("Unrule ")))
			break;

		*pos += pass("Unrule ");
		return UNRULE;
	case 'A':
		if (strncmp(str, "Add ", pass("Add ")))
			break;

		*pos += pass("Add ");
		return ADD;
	}

	printf("Invalid command at: %zu\n", *pos);
	return INVALID;
}

void
print_id(const Rid id)
{
	Rid_fn85 str;

	rid_fn85(str, id);
	printf("%s", str);
}

void
print_gap(Nit_gap *gap)
{
	Nit_crs crs;
	Rid id;

	crs_init(&crs, gap, 0);

	if (!crs_movef(&crs, sizeof(Rid))) {
		crs_read(&crs, id, sizeof(Rid));
		print_id(id);
	} else {
		printf("EMPTY");
	}

	while (!crs_movef(&crs, sizeof(Rid))) {
		printf(" ");
		crs_read(&crs, id, sizeof(Rid));
		print_id(id);
	}
}

void
print_set_vals(Nit_map *vals)
{
	Nit_iter iter;
	uint32_t key_size;
	void *id;

	iter_init(&iter, vals);

	for (; id = iter_key(&iter, &key_size); iter_next(&iter)) {
		print_id(id);
		printf(" : ");
		print_gap(iter_val(&iter));
		printf("\n");
	}
}

void
print_set_rules(Aas_rule *rules)
{
	for (; rules; rules = rules->next) {
		print_id(rules->id);
		printf(" = ");
		print_id(rules->add_id);
		printf(" { ");
		print_gap(&rules->vals);
		printf(" }\n");
	}
}

void
print_set(Aas_set *set)
{
	printf("RULES\n");
	printf("=====\n");
	print_set_rules(set->rules);
	printf("VALS\n");
	printf("====\n");
	print_set_vals(&set->vals);
}

void
repl_rule(Aas_set *set, Nit_entry **entries, const char *str, size_t pos)
{
	Aas_rule *rule;
	Aasp_err err;
	Fn85_err fn85_err;

	if (!(rule = aasp_rule(str, &pos, &err, &fn85_err))) {
		aasp_perror(err, fn85_err, pos);
		return;
	}

	switch (aas_set_rule(set, rule, entries)) {
	case -1:
		printf("Failed to add rule\n");
		aas_rule_free(rule);
		break;
	case 0:
		break;
	case 1:
		printf("Rule already present\n");
		aas_rule_free(rule);
		break;
	}
}

void
repl_unrule(Aas_set *set, Nit_entry **entries, const char *str, size_t pos)
{
	Rid id;
	Aas_rule *rule;
	Aasp_err err;
	Fn85_err fn85_err;

	if (aasp_id(id, str + pos, &pos, &err, &fn85_err) < 0) {
		aasp_perror(err, fn85_err, pos);
		return;
	}

	if ((rule = aas_set_unrule(set, id, entries)))
		aas_rule_free(rule);
	else
		printf("Rule not found\n");
}

void
repl_add(Aas_set *set, Nit_entry **entries, const char *str, size_t pos)
{
	Rid id;
	Rid rule_id;
	Aasp_err err;
	Fn85_err fn85_err;

	if (aasp_id(id, str + pos, &pos, &err, &fn85_err) < 0) {
		aasp_perror(err, fn85_err, pos);
		return;
	}

	if (str[pos] != ' ') {
		printf("Missing space at %zu\n", pos);
		return;
	}

	pos += 1;

	if (aasp_id(rule_id, str + pos, &pos, &err, &fn85_err) < 0) {
		aasp_perror(err, fn85_err, pos);
		return;
	}

	if (aas_set_add(set, id, rule_id, entries) < 0)
		fprintf(stderr, "Failed to add id\n");
}

void
repl_remove(Aas_set *set, Nit_entry **entries, const char *str, size_t pos)
{
	Rid id;
	Rid rule_id;
	Aasp_err err;
	Fn85_err fn85_err;

	if (aasp_id(id, str + pos, &pos, &err, &fn85_err) < 0) {
		aasp_perror(err, fn85_err, pos);
		return;
	}

	if (str[pos] != ' ') {
		printf("Missing space at %zu\n", pos);
		return;
	}

	pos += 1;

	if (aasp_id(rule_id, str + pos, &pos, &err, &fn85_err) < 0) {
		aasp_perror(err, fn85_err, pos);
		return;
	}

	aas_set_remove(set, id, rule_id, entries);
}

void
repl(void)
{
	Aas_set set;
	Nit_entry *entries = NULL;
	char *str = NULL;
	size_t n = 0;

	if (aas_set_init(&set, NULL, 0) < 0) {
		fprintf(stderr, "Failed to init set\n");
		return;
	}

	while (1) {
		size_t pos = 0;

		printf("* ");

		if (getline(&str, &n, stdin) < 0) {
			printf("Closing repl\n");
			break;
		}

		switch (get_command(str, &pos)) {
		case INVALID:
			continue;
		case PRINT:
			print_set(&set);
			break;
		case RULE:
			repl_rule(&set, &entries, str, pos);
			break;
		case UNRULE:
			repl_unrule(&set, &entries, str, pos);
			break;
		case ADD:
			repl_add(&set, &entries, str, pos);
			break;
		case REMOVE:
			repl_remove(&set, &entries, str, pos);
			break;
		}
	}

	free(str);
	aas_set_dispose(&set, &entries);
	nit_entries_free(entries);
}

int
main(int argc, char *argv[])
{
	repl();
	return 0;
}
